#/usr/bin/python27
#-*- coding: utf-8 -*-
#  Copyright 2011-2013 wuweihui@cn.ibm.com
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

import os, sys, re
import wx
import wx.aui
import threading
from CIMConnection.CIMlib import *
from CIMConnection import Client
from pprint import pprint
from SMSTestCase import Selector
from comm.loglib import *
try:
    from agw import customtreectrl as CT
except ImportError: # if it's not there locally, try the wxPython lib.
    import wx.lib.agw.customtreectrl as CT
import wx.lib.scrolledpanel as scrolled

DISCOVER_MODE = 'lazy'
DEFAULT_LOG_PATH = 'log/_test_log.txt'
SMS_CLASS_LIST = []
IBM_CIM_CLASS_LISt = []
def get_sms_class_list():
   global SMS_CLASS_LIST
   if not SMS_CLASS_LIST:
       with open('config/msclasslist.txt', 'rb') as fp:
           SMS_CLASS_LIST = fp.read().strip().splitlines()
   return SMS_CLASS_LIST

def get_ibm_class_list():
   global IBM_CIM_CLASS_LISt
   if not IBM_CIM_CLASS_LISt:
       with open('config/ibmcimclasslist.txt', 'rb') as fp:
           IBM_CIM_CLASS_LISt = fp.read().strip().splitlines()
   return IBM_CIM_CLASS_LISt

def sms_probe(level=2):
   if int(level) not in [0, 1, 2]:
      return
   log("Reading configuration from storage,\nThis may take few minutes, Please wait......")
   if int(level) == 0: cl = ['MSFT_SMSystem']
   elif int(level) == 1: cl = ['MSFT_SMSystem', 'MSFT_SMPool']
   else: cl = get_sms_class_list()
   for c in cl:
      ms_enumerate(c, filter='all')
   log("Read configuration finished!")

def get_instance_key_list(cn, key, dsp=None, filter='all'):
   """
   If type == 'lazy': return a empty list immediatly.
   """
   pause_logging()
   if cn.startswith('MSFT_'):
      fenumerate = ms_enumerate
   else:
      fenumerate = ms_cim_enumerate
      filter = filter != 'all' and filter or ''
   instlist = []
   try:
       for f in filter.split('|'):
          if dsp:
              instlist.extend([inst.__getattribute__(dsp)+'+'+inst.__getattribute__(key) for inst in fenumerate(cn, filter=f)])
          else:
              instlist.extend([inst.__getattribute__(key) for inst in fenumerate(cn, filter=f)])
   finally:
       resume_logging()
   return instlist

CIM_CLIENT = Client.CIMClient()
def get_cim_key_list(cn, key='path', dsp="ElementName", filter='all'):
   """
   """
   pause_logging()
   instlist = []
   try:
      if filter == 'all':
         cmd = 'cimcli -n ' + config.get('CIM Agent', 'namespace') + ' ni ' + cn
         out = CIM_CLIENT.exec_cim_cmd(cmd)
         instlist = out.splitlines()[:-1]
      else:      
       for f in filter.split('|'):
          cmd = 'cimcli -n ' + config.get('CIM Agent', 'namespace') + ' ei ' + cn + ' -niq'
          out = CIM_CLIENT.exec_cim_cmd(cmd, eCIMLogItemList)
          for inst in out:
             if inst.__hasattr__(dsp):
                instlist.append(inst.__getattribute__(dsp)+'+'+inst.__getattribute__(key))
             else:
                instlist.append(inst.__getattribute__(key))
   finally:
       resume_logging()
   return instlist

class simpledict(dict):
   """
   Dict with ordered keys, the key ordered as the input sequence
   """
   def __init__(self, dictstr=None):
      dict.__init__(self)
      self._keys = []
      if dictstr:
         self._init_items(dictstr.strip())
   
   def _init_items(self, dictstr):
      if not dictstr.startswith('{') and dictstr.endswith('}'):
         raise RuntimeError("Dict format error!")
      k, p, vs = dictstr[1:-1].partition(':')
      while p:
         vs = vs.strip()
         v = self._read_dict_value(vs)
         if vs.find(',', len(v)) != -1:
            vs = vs[vs.find(',', len(v))+1:].strip()
         else:
            vs = vs[len(v):].strip()
         if v.startswith('{'):
            v = 'simpledict("""' + v + '""")'
         self.__setitem__(eval(k.strip()), eval(v.strip()))
         k, p, vs = vs.partition(':')
      
   def _read_dict_value(self, dictstr):
      countermap = {'[': ']',
                    '{': '}',
                    '(': ')'}
      if dictstr.startswith('\"\"\"') or dictstr.startswith("\'\'\'"):
         ret = re.match(r"\"\"\".*?\"\"\"|\'\'\'.*?\'\'\'", dictstr)
         return ret.group()
      if dictstr[0] in ["\'", "\""]:
         ret = re.match(r"\".*?\"|\'.*?\'", dictstr)
         return ret.group()
      if dictstr[0] in countermap.keys():
         counter = 1
         i = 1
         while counter > 0 and i < len(dictstr):
            if dictstr[i] == dictstr[0]:
               counter += 1
            if dictstr[i] == countermap[dictstr[0]]:
               counter -= 1
            i += 1
         if counter > 0:
            raise RuntimeError("Dict format error!")
         return dictstr[:i]
      else:
         i = 0
         while i < len(dictstr):
            if dictstr[i] == ',':
               break
            i += 1
         return dictstr[:i]
      
   def __setitem__(self, i, v):
      dict.__setitem__(self, i, v)
      if i not in self._keys:
         self._keys.append(i)
   
   def keys(self):
      return self._keys
   
   def items(self):
      return [(k, self[k]) for k in self._keys]
   
   def values(self):
      return [self[k] for k in self._keys]
   
   def update(self, dc):
      for k, v in dc.items():
         self[k] = v
         
   def pop(self, key):
      dict.pop(self, key)
      self._keys.remove(key)
      
   def insert(self, i, k, v):
      dict.__setitem__(self, k, v)
      if k not in self._keys:
         self._keys.insert(i, k)
   

MSFT_FUNCTIONS = simpledict(open('config/functions.map', 'rb').read())
IBM_CIM_FUNCTIONS = simpledict(open('config/cimfunctions.map', 'rb').read())
   
LOCAL_CMD = ["discoveryLevel", "forcerediscovery", "enumerate", "enumerateFilter",
             "StgGrpbyStorageHardwareID", "StgGrpbyVolumeName", "trackJob", 
             "CIM_get", "CIM_assoc", "CIM_enum", "CIM_getSuperClass"]
LAZY_CLASS = ["MSFT_SMStorageVolume"]
LAZY_METHOD = [get_instance_key_list, get_cim_key_list]


def Courier_New(size=10):
   return wx.Font(size, wx.DEFAULT, wx.NORMAL, wx.NORMAL, False, 'Courier New')
     
class SMSPanel(wx.Panel):
   def __init__(self, parent, id=-1, frame=None):
      wx.Panel.__init__(self, parent, id)
      self.frame = frame
      
      self.searchctrl = wx.SearchCtrl(self, size=(200,-1), style=wx.TE_PROCESS_ENTER)
      self.searchctrl.ShowSearchButton(True)
      self.searchctrl.ShowCancelButton(True)
      #print MSFT_FUNCTIONS.keys()
      #Temperary solution to fix a bug in wxpython 2.8, WxListBox can not contain value start with 'c' in the list.
      self.indexlb = wx.ListBox(self, -1, style=wx.LB_SINGLE)
      self.indexlb.SetItems([' '+item for item in MSFT_FUNCTIONS.keys()])
      self.indexlb.GetStringSelection = lambda:wx.ListBox.GetStringSelection(self.indexlb).strip()
      self.indexlb.SetFont(Courier_New())
      self.indexlb.SetBackgroundColour(wx.BLACK)
      self.indexlb.SetForegroundColour(wx.GREEN)
      
      space = 5
      vsizer = wx.BoxSizer(wx.VERTICAL)
      vsizer.Add(self.searchctrl, 0, wx.EXPAND|wx.ALL, space)
      vsizer.Add(self.indexlb, 1, wx.EXPAND|wx.ALL, space)
      
      self.SetSizer(vsizer)
      
      self.indexlb.Bind(wx.EVT_LISTBOX_DCLICK, self.OnFunctionDClick)
      #self.indexlb.Bind(wx.EVT_LISTBOX, self.OnBox)
      
   def OnBox(self, evt):
       print self.indexlb.GetStringSelection()
       print dir(evt)
      
   def OnFunctionDClick(self, evt):
      if isinstance(evt, str) or isinstance(evt, unicode):
         cn = evt
      else:
         cn = self.indexlb.GetStringSelection()
         
      #print self.indexlb.GetSelection()
      #refresh creator
      cimcreator = self.frame.cimcreator
      cimcreator.Clear()
      for k, v in MSFT_FUNCTIONS[cn].items():
         if isinstance(v, list) and callable(v[0]):
            if DISCOVER_MODE == 'lazy' and v[0] in LAZY_METHOD:
                v = []
            else:
                v = v[0](*v[1:])
         cimcreator.AddParameter(k, values=v)
      cimcreator.cmdtc.SetValue(cn)
      cimcreator.funcname = cn
      
class CIMPanel(wx.Panel):
   def __init__(self, parent, id=-1, frame=None):
      wx.Panel.__init__(self, parent, id)
      self.frame = frame
      self.client = CIM_CLIENT
      
      self.searchctrl = wx.SearchCtrl(self, size=(200,-1), style=wx.TE_PROCESS_ENTER)
      self.searchctrl.ShowSearchButton(True)
      self.searchctrl.ShowCancelButton(True)
      #print MSFT_FUNCTIONS.keys()
      #Temperary solution to fix a bug in wxpython 2.8, WxListBox can not contain value start with 'c' in the list.
      self.indexlb = wx.ListBox(self, -1, style=wx.LB_SINGLE)
      self.indexlb.SetItems([' '+item for item in IBM_CIM_FUNCTIONS.keys()])
      self.indexlb.GetStringSelection = lambda:wx.ListBox.GetStringSelection(self.indexlb).strip()
      self.indexlb.SetFont(Courier_New())
      self.indexlb.SetBackgroundColour(wx.BLACK)
      self.indexlb.SetForegroundColour(wx.GREEN)
      
      space = 5
      vsizer = wx.BoxSizer(wx.VERTICAL)
      vsizer.Add(self.searchctrl, 0, wx.EXPAND|wx.ALL, space)
      vsizer.Add(self.indexlb, 1, wx.EXPAND|wx.ALL, space)
      
      self.SetSizer(vsizer)
      
      self.indexlb.Bind(wx.EVT_LISTBOX_DCLICK, self.OnFunctionDClick)
      #self.indexlb.Bind(wx.EVT_LISTBOX, self.OnBox)
      self.searchctrl.Bind(wx.EVT_TEXT, self.OnSearch)
      self.searchctrl.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN, self.OnSearchCancelBtn)
   
   def OnSearch(self, evt):
      sk = self.searchctrl.GetValue().strip().lower()
      if sk:
         matchlist = []
         if sk.count('*') == 0:
            for c in IBM_CIM_FUNCTIONS.keys():
               if sk in c.lower():
                  matchlist.append(c)
         else:
            sk = sk.replace('*', '.*')
            for c in IBM_CIM_FUNCTIONS.keys():
               if re.match(sk, c.lower()):
                  matchlist.append(c)
         self.indexlb.SetItems(matchlist)
      else:
         self.indexlb.SetItems([' '+item for item in IBM_CIM_FUNCTIONS.keys()])
         
   def OnSearchCancelBtn(self, evt):
      self.searchctrl.SetValue('')   
      
   def OnBox(self, evt):
       print self.indexlb.GetStringSelection()
       print dir(evt)
      
   def OnFunctionDClick(self, evt):
      if isinstance(evt, str) or isinstance(evt, unicode):
         cn = evt
      else:
         cn = self.indexlb.GetStringSelection()
         
      #print self.indexlb.GetSelection()
      #refresh creator
      cimcreator = self.frame.cimcreator
      cimcreator.Clear()
      if cn.count('.') > 0:
         method, self.baseclass = cn.split('.')
         cimcreator.AddParameter(self.baseclass, values=[])
      for k, v in IBM_CIM_FUNCTIONS[cn].items():
         if isinstance(v, list) and callable(v[0]):
            if DISCOVER_MODE == 'lazy' and v[0] in LAZY_METHOD:
                v = []
            else:
                v = v[0](*v[1:])
         cimcreator.AddParameter(k, values=v)
      
      cimcreator.funcname = cn.split('.')[0]
      cimcreator.cmdtc.SetValue(cimcreator.CreateCIMCommand())
      
   def ExecCMD(self, cmd):
      """
      cimcli im Sample_MethodProviderClass.Name="mooo" SayHello
              -n root/SampleProvider -ip p1=fred
      """
      if not cmd.startswith("cimcli"):
         cmd = 'cimcli -n ' + config.get('CIM Agent', 'namespace') + ' im ' + cmd
      else:
         cmd = cmd + ' -n ' + config.get('CIM Agent', 'namespace')
         if cmd.split()[1] in ["getInstance", "enumerateInstances"]:
            cmd = cmd + ' -niq'
      
      self.frame.clientnb.SetSelection(self.frame.clientnb.GetPageIndex(self.frame.sshclienttc))
      return self.client.exec_cim_cmd(cmd)
      
#   def get_cim_key_list(self, cn, key='path', dsp="ElementName", filter='all'):
#      """
#      """
#      pause_logging()
#      instlist = []
#      try:
#         if filter == 'all':
#            cmd = 'cimcli -n ' + config.get('CIM Agent', 'namespace') + ' ni ' + cn
#            out = self.client.exec_cim_cmd(cmd)
#            instlist = out.splitlines()[:-1]
#         else:      
#          for f in filter.split('|'):
#             cmd = 'cimcli -n ' + config.get('CIM Agent', 'namespace') + ' ei ' + cn + ' -niq'
#             out = self.client.exec_cim_cmd(cmd, eCIMLogItemList)
#             for inst in out:
#                if inst.__hasattr__(dsp):
#                   instlist.append(inst.__getattribute__(dsp)+'+'+inst.__getattribute__(key))
#                else:
#                   instlist.append(inst.__getattribute__(key))
#      finally:
#          resume_logging()
#      return instlist

DSCLI_CMDS = [ "applydbcheck", "lshostconnect", "mkkeymgr", "setcontactinfo", "applykey", 
              "lshosttype", "mklcu", "setdbcheck", "chauthpol", "lshostvol", "mkpe", 
              "setdialhome", "chckdvol", "lsioport", "mkpprc", "setenv", "chextpool", 
              "lskey", "mkpprcpath", "setflashrevertible", "chfbvol", "lskeygrp", "mkrank", 
              "setioport", "chhostconnect", "lskeymgr", "mkreckey", "setnetworkport", "chkeymgr", 
              "lslcu", "mkremoteflash", "setoutput", "chlcu", "lslss", "mkresgrp", "setplex", 
              "chlss", "lsnetworkport", "mksession", "setremoteflashrevertible", "chpass", 
              "lspe", "mksestg", "setrmpw", "chrank", "lsperfgrp", "mkuser", "setsim", "chresgrp", 
              "lsperfgrprpt", "mkvolgrp", "setsmtp", "chsession", "lsperfrescrpt", 
              "offloadauditlog", "setsnmp", "chsestg", "lsportprof", "offloaddbcheck", "setvpn", 
              "chsi", "lspprc", "offloadfile", "showarray", "chsp", "lspprcpath", "offloadss", 
              "showarraysite", "chsu", "lsproblem", "pausegmir", "showauthpol", "chuser", "lsrank", 
              "pausepprc", "showckdvol", "chvolgrp", "lsremoteflash", "quit", "showcontactinfo", 
              "clearvol", "lsresgrp", "resumegmir", "showenv", "closeproblem", "lsserver", 
              "resumepprc", "showextpool", "commitflash", "lssession", "resyncflash", "showfbvol", 
              "commitremoteflash", "lssestg", "resyncremoteflash", "showgmir", "cpauthpol", "lssi", 
              "reverseflash", "showgmircg", "diagsi", "lsss", "revertflash", "showgmiroos", "dscli", 
              "lsstgencl", "revertremoteflash", "showhostconnect", "echo", "lssu", "rmarray", 
              "showioport", "exit", "lsuser", "rmauthpol", "showkeygrp", "failbackpprc", "lsvolgrp", 
              "rmckdvol", "showlcu", "failoverpprc", "lsvolinit", "rmextpool", "showlss", 
              "freezepprc", "lsvpn", "rmfbvol", "shownetworkport", "help", "manageckdvol", 
              "rmflash", "showpass", "helpmsg", "managedbcheck", "rmgmir", "showplex", "initckdvol", 
              "managefbvol", "rmhostconnect", "showrank", "initfbvol", "managehostconnect", 
              "rmkeygrp", "showresgrp", "lsaddressgrp", "managekeygrp", "rmkeymgr", "showsestg", 
              "lsarray", "managepwfile", "rmlcu", "showsi", "lsarraysite", "managereckey", "rmpprc", 
              "showsp", "lsauthpol", "manageresgrp", "rmpprcpath", "showsu", "lsavailpprcport", 
              "mkaliasvol", "rmrank", "showuser", "lsckdvol", "mkarray", "rmreckey", "showvolgrp", 
              "lsda", "mkauthpol", "rmremoteflash", "testauthpol", "lsdbcheck", "mkckdvol", 
              "rmresgrp", "testcallhome", "lsddm", "mkesconpprcpath", "rmsession", "unfreezeflash", 
              "lsextpool", "mkextpool", "rmsestg", "unfreezepprc", "lsfbvol", "mkfbvol", "rmuser", 
              "ver", "lsflash", "mkflash", "rmvolgrp", "who", "lsframe", "mkgmir", "sendpe", 
              "whoami", "lsgmir", "mkhostconnect", "sendss", "lshba", "mkkeygrp", "setauthpol", ]

class DSCLIPanel(wx.Panel):
   def __init__(self, parent, id=-1, frame=None):
      wx.Panel.__init__(self, parent, id)
      self.frame = frame
      self.client = Client.DSCLIClient()
      self.devids = []
      
      self.searchctrl = wx.SearchCtrl(self, size=(200,-1), style=wx.TE_PROCESS_ENTER)
      self.searchctrl.ShowSearchButton(True)
      self.searchctrl.ShowCancelButton(True)
      #print MSFT_FUNCTIONS.keys()
      #Temperary solution to fix a bug in wxpython 2.8, WxListBox can not contain value start with 'c' in the list.
      self.indexlb = wx.ListBox(self, -1, style=wx.LB_SINGLE|wx.LB_SORT)
      self.indexlb.SetItems([' '+item for item in DSCLI_CMDS])
      self.indexlb.GetStringSelection = lambda:wx.ListBox.GetStringSelection(self.indexlb).strip()
      self.indexlb.SetFont(Courier_New())
      self.indexlb.SetBackgroundColour(wx.BLACK)
      self.indexlb.SetForegroundColour(wx.GREEN)
      
      space = 5
      vsizer = wx.BoxSizer(wx.VERTICAL)
      vsizer.Add(self.searchctrl, 0, wx.EXPAND|wx.ALL, space)
      vsizer.Add(self.indexlb, 1, wx.EXPAND|wx.ALL, space)
      
      self.SetSizer(vsizer)
      
      self.indexlb.Bind(wx.EVT_LISTBOX_DCLICK, self.OnFunctionDClick)
      #self.indexlb.Bind(wx.EVT_LISTBOX, self.OnBox)
      self.searchctrl.Bind(wx.EVT_TEXT, self.OnSearch)
      self.searchctrl.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN, self.OnSearchCancelBtn)

   def init_items(self):
       pass
    
   def OnSearch(self, evt):
      sk = self.searchctrl.GetValue().strip()
      if sk:
         matchlist = []
         if sk.count('*') == 0:
            for c in DSCLI_CMDS:
               if sk in c:
                  matchlist.append(c)
         else:
            sk = sk.replace('*', '.*')
            for c in DSCLI_CMDS:
               if re.match(sk, c):
                  matchlist.append(c)
         self.indexlb.SetItems(matchlist)
      else:
         self.indexlb.SetItems([' '+item for item in DSCLI_CMDS])
         
   def OnSearchCancelBtn(self, evt):
      self.searchctrl.SetValue('')
      
   def OnBox(self, evt):
       print self.indexlb.GetStringSelection()
       print dir(evt)
      
   def OnFunctionDClick(self, evt):
      self._SetDefaultDevID()
      if isinstance(evt, str) or isinstance(evt, unicode):
         cn = evt
      else:
         cn = self.indexlb.GetStringSelection()
         
      helpinfo = Client.get_dscli_cmd_help(cn)
      self.frame.helptc.SetValue(helpinfo)
      self.frame.clientnb.SetSelection(self.frame.clientnb.GetPageIndex(self.frame.helptc))
      
      pardict = Client.get_cmd_parameters_from_help_info(helpinfo)
      pardict = self._merge_options(pardict)
      
      cimcreator = self.frame.cimcreator
      cimcreator.Clear()
      
      cimcreator.funcname = cn
      for k, v in pardict.items():
         default = ''
         values = v[1:]
         if k == 'dev':
            default = self.devids and self.devids[0] or ''
            values = self.devids or v[1:]
         key = '-' + k
         if key == '-dev' or v[0] == True:
            key += '*'
         cimcreator.AddParameter(key, default, values=values)
      cimcreator.cmdtc.SetValue(cn)
      if cimcreator.pardict.has_key('-dev*'):
         cimcreator.cmdtc.SetValue(cimcreator.CreateDSCLICommand())
   
   def _SetDefaultDevID(self):      
      if not self.devids:
         log('Initiating, please wait... ...')
         try:
            silist = self.client.dscli('lssi', parser=LSLogItemList)
         except:
            silist = []
         if silist:
            self.devids = [item.ID for item in silist]
   
   def _merge_options(self, pardict):
      """
      1. move main to last
      2. merge the options with out values
      """
      keys = []
      mv = []
      for k, v in pardict.items():
         if k == 'main':
            pardict.pop('main')
            mv = v
         if v[0] == False and len(v) == 1:
            keys.append('-' + k)
            pardict.pop(k)
      if keys:
         pardict.insert(0, 'poptions', [False]+keys)
      if mv:
         pardict['main'] = mv
      return pardict
   
   def ExecCMD(self, cmd):
      self.frame.clientnb.SetSelection(self.frame.clientnb.GetPageIndex(self.frame.sshclienttc))
      self.client.dscli_without_wait(cmd)


class SSHClientTC(wx.TextCtrl):
   def __init__(self, parent, logfp=None, id=-1, frame=None):
      wx.TextCtrl.__init__(self, parent, id, '', style=wx.TE_MULTILINE|wx.TE_DONTWRAP)
      self.frame = frame
      self.SetFont(Courier_New())
      self.SetEditable(False)
      self.SetBackgroundColour(wx.BLACK)
      self.SetForegroundColour(wx.GREEN)
      #self.CaptureOutput()
      if isinstance(logfp, basestring):
         self.logfp = open(logfp, 'wb')
      else:
         self.logfp = logfp

   def write(self, txt):
       """
       1. Fix the windows encoding problem
       2. Bypass a bug in wxpython 2.8, TextCtrl will miss some text when writing long string at one time 
       """
       #self.systemout.write(txt)
       if self.logfp:
          self.logfp.write(txt)
       if isinstance(txt, str):
          txt = unicode(txt, 'utf-8')
       while txt:
          wx.TextCtrl.write(self, txt[:1000])
          txt = txt[1000:]
       
   def flush(self):
      """
      simulate a file object
      """
      if self.logfp:
          self.logfp.flush()
          
   def SetLogPath(self, path):
      if isinstance(path, basestring):
         self.logfp = open(path, 'wb')
      else:
         self.logfp = path
      
   def CaptureOutput(self):      
      set_log_path(self, self)
      #redirect_output(self, self)
   
   def ReleaseOutput(self):
      sys.stdout = sysout
      sys.stderr = syserr

class CIMCreater(wx.Panel):
   def __init__(self, parent, id=-1, frame=None):
      wx.Panel.__init__(self, parent, id, style=wx.TAB_TRAVERSAL|wx.CLIP_CHILDREN)
      self.SetFont(Courier_New())
      #self.SetBackgroundColour(wx.BLACK)
      self.SetBackgroundColour(wx.Colour(84,84,84))
      self.SetForegroundColour(wx.GREEN)
      self.frame = frame
      self.pardict = simpledict()
      self.funcname = None
      self.srvisset = False
      self.timeout = None
            
      self.parameterpanel = scrolled.ScrolledPanel(self, -1)
      self.vsizer = wx.BoxSizer(wx.VERTICAL)
      self.parameterpanel.SetSizer(self.vsizer)
            
      self.cmdtc = wx.TextCtrl(self, -1, '')
      execbtn = wx.Button(self, -1, 'Execute')
      execbtn.SetForegroundColour(wx.RED)
      
      mainsizer = wx.BoxSizer(wx.VERTICAL)
      #self.vsizer = wx.BoxSizer(wx.VERTICAL)
      space = 5
      hsizer = wx.BoxSizer(wx.HORIZONTAL)
      hsizer.Add(self.cmdtc, 1, wx.EXPAND|wx.ALL, space)
      hsizer.Add(execbtn, 0, wx.EXPAND|wx.ALL, space)
      
      mainsizer.Add(self.parameterpanel, 1, wx.EXPAND|wx.ALL, 0)
      mainsizer.Add(hsizer, 0, wx.EXPAND|wx.ALL, 0)
      self.SetSizer(mainsizer)
           
      self.Update()
      
      execbtn.Bind(wx.EVT_BUTTON, self.OnExecute)
      #self.Disable()
      
   def AddParameter(self, pn, default='', values=[]):
      length = 15
      pnst = wx.StaticText(self.parameterpanel, -1, (pn+':').ljust(length))
      pnst.SetFont(Courier_New())
      pnst.SetForegroundColour(wx.GREEN)
      pcb = wx.ComboBox(self.parameterpanel, -1, default, choices=values, 
                        style=wx.CB_SORT|wx.CB_DROPDOWN|wx.TE_PROCESS_ENTER)
      pcb.GetValue = lambda:wx.ComboBox.GetValue(pcb)[wx.ComboBox.GetValue(pcb).find('+')+1:]
      pcb.pname = pn
      
      space = 5
      hsizer = wx.BoxSizer(wx.HORIZONTAL)
      hsizer.Add(pnst, 0, wx.EXPAND|wx.ALL, space)
      hsizer.Add(pcb, 1, wx.EXPAND|wx.ALL, space)
      self.vsizer.Add(hsizer, 0, wx.EXPAND|wx.ALL, 0)
      self.parameterpanel.GetSizer().Layout()
      self.GetSizer().Layout()
      #self.Update()
      
      #pcb.Bind(wx.EVT_COMBOBOX, self.OnCombo)
      pcb.Bind(wx.EVT_TEXT, self.OnValueChange)
      pcb.Bind(wx.EVT_TEXT_ENTER, self.OnTextEnter)
      pcb.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.OnMouseLost)
      
      self.pardict[pn] = pcb
      
   def OnCombo(self, evt): 
      pcb = evt.GetEventObject()
      print dir(pcb)
      pcb.Update()
      
   def OnTextEnter(self, evt):
      """
      Perform lazy probe for MS-SMS
      Get help informatin for DSCLI
      """
      pcb = evt.GetEventObject()
      if self.frame.operationnb.GetPageText(self.frame.operationnb.GetSelection()) == 'DSCLI':
         pn = pcb.pname
         if pn.endswith('*'):
            pn = pn[:-1]
         if pn in ['-poptions']:
            return
         if pn == '-main':
            self.ShowKeyHelpInfo('Example\n')
         self.ShowKeyHelpInfo(pn)
      elif self.frame.operationnb.GetPageText(self.frame.operationnb.GetSelection()) == 'CIMCLI':
         if pcb.pname.startswith("IBMTS"):
            vs = get_cim_key_list(pcb.pname)
            pcb.SetItems(vs)
         else:
            #name = pcb.pname.replace("(REF)", '')
            v = IBM_CIM_FUNCTIONS[self.funcname + '.' + self.frame.cimpanel.baseclass][pcb.pname]
            if isinstance(v, list) and callable(v[0]) \
            and len(v) > 1:
               filter = pcb.GetValue().strip()
               vs = v[0](*v[1:4]+[filter])
               if pcb.pname.count('(REF)') > 0:
                  vs = ['[' + item + ']' for item in vs]
               pcb.SetItems(vs)
      else:
         v = MSFT_FUNCTIONS[self.funcname][pcb.pname]
         if isinstance(v, list) and callable(v[0]) \
         and len(v) > 1:
            filter = pcb.GetValue().strip()
            vs = v[0](*v[1:4]+[filter])
            pcb.SetItems(vs)
   
   def ShowKeyHelpInfo(self, sk):
      src = self.frame.helptc.GetValue()
      spos = src.find('\nParameters\n')
      if spos == -1:
         return
      ret = re.search('\n\s*' + sk, src, re.DOTALL)
      #pos = src.find('\n'+sk, spos)
      #print sk, pos
      #if pos != -1:
      if ret:
         pos = ret.start()
         #self.frame.helptc.SetFocus()
         ppos = pos + src[:pos].count('\n') + 2
         self.frame.helptc.ShowPosition(ppos+len(sk))
         self.frame.helptc.SetSelection(ppos, ppos+len(sk))
         
      
   def OnValueChange(self, evt):
      #flb = self.frame.smspanel.indexlb
      if self.frame.operationnb.GetPageText(self.frame.operationnb.GetSelection()) == 'DSCLI':
         cmd = self.CreateDSCLICommand()
      elif self.frame.operationnb.GetPageText(self.frame.operationnb.GetSelection()) == 'CIMCLI':
         cmd = self.CreateCIMCommand()
      else:
         args = []
         for k, v in self.pardict.items():
            if k == 'Timeout':
               self.timeout = v.GetValue().strip() and int(v.GetValue().strip()) or None
            else:
               args.append(v.GetValue().strip())
         #args = [v.GetValue().strip() for v in self.pardict.values()]
         if self.funcname not in LOCAL_CMD:
            args += [config.get('CLI server', 'user'), config.get('CLI server', 'passwd')]
         cmd = ' '.join([self.funcname] + args)
      self.cmdtc.SetValue(cmd)
      
      #pop up option
      pcb = evt.GetEventObject()
      if pcb.GetValue().strip():
         #pcb.SetWindowStyleFlag(wx.CB_SORT|wx.CB_SIMPLE|wx.TE_PROCESS_ENTER)
         pass
      else:
         pass
      
      evt.Skip()
      
   def CreateDSCLICommand(self):
      args = []
      for k, v in self.pardict.items():
         if k in ['-poptions', '-main', '-poptions*', '-main*']:
            value = v.GetValue().strip()
            if value:
               args.append(value)
         else:
            if k.endswith('*'):
               k = k[:-1]
            value = v.GetValue().strip()
            if value:
               args += [k, value]
      cmd = ' '.join([self.funcname] + args)
      return cmd
   
   def CreateCIMCommand(self):
      """
      cimcli im Sample_MethodProviderClass.Name="mooo" SayHello
              -n root/SampleProvider -ip p1=fred
      """
      if self.funcname != "cimcli":
         cmd = self.pardict.values()[0].GetValue().strip().replace('"', '\\"') + ' ' + self.funcname + ' -ip '
         for k, v in self.pardict.items():
            k = k.replace("(REF)", "")
            if not k.startswith('IBMTS'):
               value = v.GetValue().strip()
               if value and value == 'NULL':
                  cmd = cmd + k + '='  + ' '
               elif value:
                  cmd = cmd + k + '=' + value + ' '
         if cmd.endswith(' -ip '):
            cmd = cmd[:-5]
      else:
         cmd = self.funcname + " "
         for k, v in self.pardict.items():
            value = v.GetValue().strip()
            cmd += value + " "
      return cmd
      
   def OnMouseLost(self, evt):
      pcb = evt.GetEventObject()
      print pcb.GetValue()
      evt.Skip()
      
   def OnExecute(self, evt):
      cmd = self.cmdtc.GetValue()
      if cmd.startswith('debug'):
          log(cmd)
          script = cmd[len('debug'):].strip()
          eval(script)
          return
      if cmd.startswith('probe'):
          def _probe(level):
              self.Disable()
              self.frame.sshclienttc.SetValue("")
              try:
                  sms_probe(level)
                  with open('log/probe.log', 'wb') as fp:
                      fp.write(self.frame.sshclienttc.GetValue())
                  self.frame.sshclienttc.SetValue("Probe Finished Successfully at level %s!\n" % level)
              finally:
                  self.Enable()
          threading.Thread(target=_probe, args=(cmd.replace('probe', '').strip() or 2,)).start()
          return
      #Check the CIM explore command condition, set target server if necessary
      if cmd.startswith('CIM_'): 
         cmd = cmd[len("CIM_"):]
         if not cmd.startswith('srv') and not self.srvisset:
            srvcmd = ' '.join(['srv', config.get('CIM Agent', 'server')+':'+config.get('CIM Agent', 'srvport'),
                               config.get('CIM Agent', 'namespace'), 
                               config.get('CLI server', 'user'), config.get('CLI server', 'passwd')])
            exec_sms_cmd(srvcmd)
            self.srvisset = True         
         
      def _exec_cmd(cmd):
          self.Disable()
          try:
              if self.frame.operationnb.GetPageText(self.frame.operationnb.GetSelection()) == 'DSCLI':
                 out = self.frame.dsclipanel.ExecCMD(cmd)
              elif self.frame.operationnb.GetPageText(self.frame.operationnb.GetSelection()) == 'CIMCLI':
                 out = self.frame.cimpanel.ExecCMD(cmd)
              else:
                 out = exec_sms_cmd(cmd)
                 if cmd.startswith('srv'):
                    self.srvisset = True
                 if self.timeout and cmd.startswith('startDiscovery'):
                    wait_until_discovery_finish(self.timeout)
                    self.timeout = None
          finally:
              self.Enable()
      threading.Thread(target=_exec_cmd, args=(cmd,)).start()
          
   def UnLock(self):
       if not self.IsEnabled():
           self.Enable()
      
   def Clear(self):
      self.pardict = simpledict()
      self.cmdtc.SetValue('')
      self.vsizer.Clear(True)
      self.Update()

class CaseTreeCtrl(CT.CustomTreeCtrl):

    def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
                 size=wx.DefaultSize,
                 style=wx.SUNKEN_BORDER|wx.WANTS_CHARS,
                 agwStyle=CT.TR_HAS_BUTTONS|CT.TR_HAS_VARIABLE_ROW_HEIGHT,
                 log=None):

        CT.CustomTreeCtrl.__init__(self, parent, id, pos, size, style, agwStyle)
        self.SetBackgroundColour(wx.BLACK)
        self.SetForegroundColour(wx.GREEN)
        
        self.root = self.AddRoot("Case List", ct_type=1)
        self.SetPyData(self.root, None)
        
        self.Bind(CT.EVT_TREE_ITEM_CHECKED, self.OnCheckItem)
        
    def get_case_dict(self, parent=None):
        casedict = {}
        parent = parent or self.root
        (child, cookie) = self.GetFirstChild(parent)
        while child:
            if self.HasChildren(child):
                casedict.update(self.get_case_dict(child))
            elif child.IsChecked():
                k = parent.GetData() or parent.GetText()
                v = child.GetData() or child.GetText()
                if casedict.has_key(k):
                  casedict[k].append(v)
                else:
                  casedict[k] = [v]
            (child, cookie) = self.GetNextChild(parent, cookie)
        return casedict
     
    def create_tree(self, caselist):
      for case in caselist:
         cn = case.__name__
         caseitem = self.AppendItem(self.root, cn, ct_type=1)
         self.SetPyData(caseitem, case)
         methods = Selector.read_test_methods(case)
         for method in methods:
            methoditem = self.AppendItem(caseitem, method, ct_type=1)
            self.SetPyData(methoditem, None)
            
    def OnCheckItem(self, evt):
       item = evt.GetItem()
       #print dir(evt)
       self.CheckChilds(item, item.IsChecked())
       self.AutoCheckParent(item, item.IsChecked())
            
class TestCasePanel(wx.Panel):
   def __init__(self, parent, id=-1, frame=None):
      wx.Panel.__init__(self, parent, id)
      self.frame = frame
      self.result = None
      
      sizer = wx.BoxSizer(wx.VERTICAL)
      
      self.treectrl = CaseTreeCtrl(self)
      sizer.Add(self.treectrl, 1, wx.EXPAND|wx.ALL, 0)
      self.SetSizer(sizer)
      
      self.treectrl.create_tree(Selector.read_test_cases('SMSTestCase'))
      
      self.runbutton = wx.Button(self, -1, 'RunCase')
      sizer.Add(self.runbutton, 0, wx.EXPAND|wx.ALL, 0)
      stopbutton = wx.Button(self, -1, 'Stop')
      sizer.Add(stopbutton, 0, wx.EXPAND|wx.ALL, 5)
      sizer.Layout()
      
      self.Bind(wx.EVT_BUTTON, self.OnRun, self.runbutton)
      self.Bind(wx.EVT_BUTTON, self.OnStop, stopbutton)
      
   def OnRun(self, evt):
#      for k, v in self.treectrl.get_case_dict().items():
#         print k, v
      def _runtest():
         self.runbutton.Disable()
         #out, err = set_log_path("log/_autotestlog.log", redirect=False)
         try:
            self.result = Selector.run_suite(casedict, stream=self.frame.reporttc)
         finally:
            self.runbutton.Enable()
            #set_log_path(out, redirect=False)
            self.frame.reporttc.flush()
      casedict = self.treectrl.get_case_dict()
      if casedict:
         threading.Thread(target=_runtest, args=()).start()
      
      
   def OnStop(self, evt):
      if self.result:
         self.result.Stop()

class TestFrame(wx.Frame):
   def __init__(self, parent, id=-1):
      wx.Frame.__init__(self, parent, id, 'SMS Test Tool Beta by wuweihui@cn.ibm.com', size = (970, 720))
      self.SetMinSize((640,480))
      
      #self.panel = wx.Panel(self, -1)
      #self.panel.SetFont(Courier_New())
      
      self.cimcreator = CIMCreater(self, frame=self)
      self.smspanel = SMSPanel(self, frame=self)
      self.sshclienttc = SSHClientTC(self, frame=self)
      
      space = 5
      vsizer = wx.BoxSizer(wx.VERTICAL)
      
      hsizer = wx.BoxSizer(wx.HORIZONTAL)
      hsizer.Add(self.smspanel, 0, wx.EXPAND|wx.ALL, space)
      hsizer.Add(self.sshclienttc, 1, wx.EXPAND|wx.ALL, space)
      
      vsizer.Add(hsizer, 1, wx.EXPAND|wx.ALL, 0)
      vsizer.Add(self.cimcreator, 1, wx.EXPAND|wx.ALL, 0)
      
      self.SetSizer(vsizer)
      
      self.CreateMenu()
      
   def CreateMenu(self):
      self.ID_REALMODE = wx.NewId()
      self.ID_LAZYMODE = wx.NewId()
      menubar = wx.MenuBar()
      modemenu = wx.Menu()
      modemenu.Append(self.ID_REALMODE, "&Lazy", "Lazy mode", wx.ITEM_RADIO)
      modemenu.Append(self.ID_LAZYMODE, "&Real", "Real mode", wx.ITEM_RADIO)
      menubar.Append(modemenu, '&Mode')
      
      self.SetMenuBar(menubar)
      
      self.Bind(wx.EVT_MENU, self.OnModeChange, id=self.ID_REALMODE)
      self.Bind(wx.EVT_MENU, self.OnModeChange, id=self.ID_LAZYMODE)
      
   def OnModeChange(self, evt):
      global DISCOVER_MODE
      if evt.GetId() == self.ID_REALMODE:
         DISCOVER_MODE = 'real'
      else:
         DISCOVER_MODE = 'lazy'

      
class SMSToolFrame(wx.Frame):
   def __init__(self, parent, id=-1):
      wx.Frame.__init__(self, parent, id, 'SMS Test Tool Beta by wuweihui@cn.ibm.com', size = (970, 720))
      self.SetMinSize((640,480))
      self.panel = wx.Panel(self, -1)
      self.searchpage = None
      
      self.mgr = wx.aui.AuiManager()
      self.mgr.SetManagedWindow(self.panel)
      
      self.SetStatusBar(wx.StatusBar(self, -1))
      self.StatusBar.SetFieldsCount(2)
      self.StatusBar.SetStatusWidths([-2, -1])
      self.SetStatusText("wuweihui@cn.ibm.com", 1)

      self.cimcreator = CIMCreater(self.panel, frame=self)
      self.smspanel = SMSPanel(self.panel, frame=self)
      self.sshclienttc = SSHClientTC(self.panel, frame=self)
      self.sshclienttc.Bind(wx.EVT_KEY_DOWN, self.OnTextFind)
      self.reporttc = SSHClientTC(self.panel, frame=self)
      self.sshclienttc.CaptureOutput()
      self.sshclienttc.SetLogPath(DEFAULT_LOG_PATH)
      self.reporttc.SetLogPath('log/_report.log')
      self.helptc = wx.TextCtrl(self.panel, -1, '', style=wx.TE_MULTILINE)
      self.helptc.SetFont(Courier_New())
      self.helptc.SetEditable(False)
      self.helptc.SetBackgroundColour(wx.BLACK)
      self.helptc.SetForegroundColour(wx.GREEN)
      
      self.casetree = TestCasePanel(self.panel, frame=self)
      
      self.cimpanel = CIMPanel(self.panel, frame=self)
      
      self.dsclipanel = DSCLIPanel(self.panel, frame=self)
      
      self.operationnb = wx.aui.AuiNotebook(self.panel, style=wx.aui.AUI_NB_BOTTOM | wx.aui.AUI_NB_TAB_SPLIT \
                                            | wx.aui.AUI_NB_TAB_MOVE | wx.aui.AUI_NB_SCROLL_BUTTONS)
      self.operationnb.AddPage(self.smspanel, 'MS-SMS')
      self.operationnb.AddPage(self.cimpanel, 'CIMCLI')
      self.operationnb.AddPage(self.dsclipanel, 'DSCLI')
      self.operationnb.AddPage(self.casetree, 'TestCase')
      self.operationnb.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGED, self.OnOperationChange)
      
      self.clientnb = wx.aui.AuiNotebook(self.panel, style=wx.aui.AUI_NB_BOTTOM | wx.aui.AUI_NB_TAB_SPLIT \
                                         | wx.aui.AUI_NB_TAB_MOVE | wx.aui.AUI_NB_SCROLL_BUTTONS)
      self.clientnb.AddPage(self.sshclienttc, 'SSHClient')
      self.clientnb.AddPage(self.reporttc, 'Report')
      self.clientnb.AddPage(self.helptc, 'HelpInfo')
      
      self.findext = wx.Panel(self.panel, style=wx.TAB_TRAVERSAL|wx.CLIP_CHILDREN)
      self.findtextctrl = wx.TextCtrl(self.findext, -1, '', size=(200, -1))
      findnextbutton = wx.Button(self.findext, -1, 'Next')
      extsizer = wx.BoxSizer(wx.HORIZONTAL)
      extsizer.Add(self.findtextctrl, 1, wx.EXPAND|wx.ALL, 5)
      extsizer.Add(findnextbutton, 0, wx.EXPAND, 5)
      self.findext.SetSizer(extsizer)
      self.findext.Fit()
      findnextbutton.Bind(wx.EVT_BUTTON, self.OnFindText)
      
      self.mgr.AddPane(self.clientnb, wx.aui.AuiPaneInfo().CenterPane().Name("Client"))
      self.mgr.AddPane(self.operationnb,
                       wx.aui.AuiPaneInfo().
         Left().Layer(2).BestSize(wx.Size(240, -1)).
         MinSize((160, -1)).
         Floatable(False).
         Caption("Operations").
         CloseButton(False).
         Name("Operations"))      
      self.mgr.AddPane(self.cimcreator,
                       wx.aui.AuiPaneInfo().
         Bottom().Layer(2).
         CloseButton(False).BestSize(wx.Size(200, 300)).
         MinSize(wx.Size(200,100)).
         Caption("Parameters").
         Name("CIMCreator"))
      self.mgr.AddPane(self.findext,
                       wx.aui.AuiPaneInfo().
         Right().Layer(3).Position(3).Row(3).
         Floatable(True).
         CloseButton(True).
         Float().Hide().
         Name("FindText"))
      self.mgr.Update()      
      self.mgr.SetFlags(self.mgr.GetFlags() ^ wx.aui.AUI_MGR_TRANSPARENT_DRAG)
      
      self.CreateMenu()
      
      
   def CreateMenu(self):
      self.ID_REALMODE = wx.NewId()
      self.ID_LAZYMODE = wx.NewId()
      menubar = wx.MenuBar()
      modemenu = wx.Menu()
      modemenu.Append(self.ID_REALMODE, "&Lazy", "Lazy mode", wx.ITEM_RADIO)
      modemenu.Append(self.ID_LAZYMODE, "&Real", "Real mode", wx.ITEM_RADIO)
      menubar.Append(modemenu, '&Mode')
      
      ID_ENABLELOG = wx.NewId()
      ID_SETLOGPATH = wx.NewId()
      logmenu = wx.Menu()
      logmenu.Append(ID_ENABLELOG, "&Enable Log", "Enable Log", wx.ITEM_CHECK).Check()
      
      logmenu.Append(ID_SETLOGPATH, "&Set Log Path", "Set Log Path")
      menubar.Append(logmenu, '&Log')
      
      self.SetMenuBar(menubar)
      
      self.Bind(wx.EVT_MENU, self.OnModeChange, id=self.ID_REALMODE)
      self.Bind(wx.EVT_MENU, self.OnModeChange, id=self.ID_LAZYMODE)
      
      self.Bind(wx.EVT_MENU, self.OnEnableLog, id=ID_ENABLELOG)
      self.Bind(wx.EVT_MENU, self.OnSetLogPath, id=ID_SETLOGPATH)
      
   def OnModeChange(self, evt):
      global DISCOVER_MODE
      if evt.GetId() == self.ID_REALMODE:
         DISCOVER_MODE = 'real'
      else:
         DISCOVER_MODE = 'lazy'
         
   def OnEnableLog(self, evt):
      if evt.IsChecked():
         self.sshclienttc.SetLogPath(DEFAULT_LOG_PATH)
      else:
         self.sshclienttc.SetLogPath(None)
   
   def OnSetLogPath(self, evt):
        wildcard = "TXT files (*.txt)|*.txt|"        \
                   "All files (*.*)|*.*"
        dlg = wx.FileDialog(
            self, message="Save file as ...", defaultDir=os.getcwd(), 
            defaultFile="", wildcard=wildcard, style=wx.SAVE
            )

        if dlg.ShowModal() == wx.ID_OK:
            path = dlg.GetPath()
            self.sshclienttc.SetLogPath(path)
                        
        dlg.Destroy()
        
   def OnTextFind(self, evt):
   #print dir(evt)
      if evt.ControlDown() and evt.GetKeyCode() == 70:
         self.searchpage = evt.GetEventObject()
         self.fstartpos = self.searchpage.GetInsertionPoint()
         
         pt = self.searchpage.ClientToScreen(wx.Point(0, 0))
         self.mgr.GetPane("FindText").Show().FloatingPosition((pt.x+50, pt.y+50))
         self.mgr.Update()
         self.findtextctrl.SetFocus()
      evt.Skip()
      
   def OnFindText(self, evt):
      sk = self.findtextctrl.GetValue().lower().strip()
      if sk:
         src = self.searchpage.GetValue().lower()
         pos = src.find(sk, self.fstartpos)
         #print sk, pos
         if pos != -1:
            self.searchpage.SetFocus()
            ppos = pos + src[:pos].count('\n')
            self.searchpage.ShowPosition(ppos+len(sk))
            self.searchpage.SetSelection(ppos, ppos+len(sk))
            self.fstartpos = pos + len(sk)
         else:
            if self.fstartpos == 0:
               return
            else:
               self.fstartpos = 0
      
   def OnOperationChange(self, evt):
      self.cimcreator.Clear()
      pn = self.operationnb.GetPageText(evt.GetSelection())
      if pn == 'DSCLI':
         self.dsclipanel.init_items()
      
         
class TestApp(wx.App):

   def OnInit(self):
      self.frame = SMSToolFrame(None)
      self.frame.Show(True)
      return True
      
if __name__ == "__main__":
   app = TestApp(False)
   app.MainLoop()
   
   